package org.dei.perla.sys.device.channel.concrete.socket;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.channels.ClosedByInterruptException;
import java.util.ArrayList;

import org.dei.perla.sys.configuration.ConfigurationParameter;
import org.dei.perla.sys.device.channel.Channel;
import org.dei.perla.sys.device.channel.ChannelManager;
import org.dei.perla.sys.device.channel.frame.FrameConfiguration;
import org.dei.perla.sys.device.channel.frame.address.FrameAddress;
import org.dei.perla.sys.device.channel.frame.address.concrete.SocketFrameAddress;
import org.dei.perla.sys.device.channel.frame.receiver.FrameReceiverHeaderLess;
import org.dei.perla.sys.device.channel.frame.sender.FrameSenderHeaderLess;
import org.dei.perla.utils.logger.LogRecord.Type;
import org.dei.perla.utils.logger.LogRecord.Verbosity;

/**
 * Rappresenta un ChannelManager per canali di tipo socket
 *
 */
public class SocketChannelManager extends ChannelManager {
	
	/**
	 * Costanti di configurazione del parser dell'XML di configurazione
	 */
	private final static String PARAM_LOCAL_PORT 	= "local-server-port";
	
	/**
	 * Contiene il thread per gestire connessioni remote in arrivo
	 */
	private Thread pThreadLocalServer;
	
	/**
	 * Porta locale su cui il server si mette in ascolto
	 */
	private int pLocalPort;
	
	/**
	 * Costruttore, inizializza un SocketChannelManager
	 * 
	 * @param parChannelId
	 * @param parName
	 * @param parChannel
	 * @param parFrameConfiguration
	 * @param parParams
	 */
	public SocketChannelManager(int parChannelId, String parName, ArrayList<Channel> parChannel,
			FrameConfiguration parFrameConfiguration, ArrayList<ConfigurationParameter> parParams) {
		super(parChannelId, parName, parChannel, parFrameConfiguration);
	
		//  Leggi i parametri di configurazione
		for (int i = 0; i < parParams.size(); i++) {
			
			ConfigurationParameter tCurrParam = parParams.get(i);
			
			if ( tCurrParam.getName().equalsIgnoreCase(SocketChannelManager.PARAM_LOCAL_PORT)) {
				this.pLocalPort = new Integer(tCurrParam.getValue()).intValue();
			} 
		
		}
	}
	
	/**
	 * Costruttore, inizializza un SocketChannelManager
	 * 
	 * @param parChannelId
	 * @param parName
	 * @param parChannel
	 * @param parFrameConfiguration
	 * @param parLocalPort
	 */
	public SocketChannelManager(int parChannelId, String parName, ArrayList<Channel> parChannel,
			FrameConfiguration parFrameConfiguration, int parLocalPort) {
		super(parChannelId, parName, parChannel, parFrameConfiguration);
	
		this.pLocalPort = parLocalPort;
	}
	
	@Override
	public void start() {
		
		// Verifica che il ChannelManager non sia già stato avviato
		if(!this.isStarted()) {
			super.start();
		
			// Avvia il thread per gestire le connessioni in ingresso
			this.pThreadLocalServer = new Thread() {
				public void run() {
					pThreadLocalServerFunction();
				}
			};
			this.pThreadLocalServer.start();
			
		}
		
	}
	@Override
	public void stop() {
		
		// Verifica che il ChannelManager non sia già stato avviato
		if(this.isStarted()) {
		
			// Avvia il thread per gestire le connessioni in ingresso
			this.pThreadLocalServer.interrupt();
			
			super.stop();
			
		}
		
	}
	
	@Override
	protected Channel pCreateChannel(FrameAddress parAddress) {
		
		return new SocketChannel(this.getName() + "- Canale socket ( " + ((SocketFrameAddress) parAddress).getAddress() + " )", 
				new FrameReceiverHeaderLess(this.pFrameConfiguration),
				new FrameSenderHeaderLess(this.pFrameConfiguration), 
				new SocketChannelConfiguration(((SocketFrameAddress) parAddress).getAddress().getAddress().getHostAddress(), this.pLocalPort ));

	}
	
	/**
	 * Gestisce le richieste di connessioni in ingresso al SocketChannelManager
	 */
	private void pThreadLocalServerFunction() {
		
		ServerSocket tServerSocket 	= null;
		Socket tTempSocket 			= null;
		int tSocketCounter = 0;
		String tSocketDescription;
		
		// Costruisce una stringa di descrizione per il socket
		tSocketDescription = "socket ";
		tSocketDescription += "(" + this.pLocalPort + ")";

		// Aggiunge una notifica nel log
		this.loggerNotify(Type.Information, "Apertura server locale " + tSocketDescription
				+ " in corso", Verbosity.High);
		
		
		try {
			
			// Crea una nuova socket server
			tServerSocket = new ServerSocket(this.pLocalPort) ;

		} // Segnala eventuali errori legati al nome non valido dell'host
		catch (UnknownHostException e) {
			this.loggerNotify(Type.Error, "Impossibile aprire il "
					+ tSocketDescription, e.getMessage(), Verbosity.Low);
		}
		
		// Segnala eventuali errori di IO
		catch (IOException e) {
			this.loggerNotify(Type.Error, "Impossibile aprire il "
					+ tSocketDescription, e.getMessage(), Verbosity.Low);
		}
	
		// Segnala eventuali altri errori
		catch (Exception e) {
			this.loggerNotify(Type.Error,
							"Si sono verificati dei problemi durante l'apertura del "
									+ tSocketDescription, e.getMessage(),
							Verbosity.Low);
		}
		
		
		

		// Attendi una socket in ingresso
		try {
			while ((tTempSocket = tServerSocket.accept())!= null) {
				
				SocketChannel tTempChannel = new SocketChannel(this.getName() + " - Canale generato " + tSocketCounter, new FrameReceiverHeaderLess(this.pFrameConfiguration), 
						new FrameSenderHeaderLess(this.pFrameConfiguration), tTempSocket);
				
				this.pRegisterChannel(tTempChannel, null);
				
				// Aggiungi il canale alla lista di canali
				this.pChannel.add(tTempChannel);
				
				tSocketCounter++;
				this.loggerNotify(Type.Information, "Accettata una socket in ingresso, creato un nuovo canale", Verbosity.High);
			
			}
			
		// E' stato richiesto al thread di terminare.
		} catch (ClosedByInterruptException e) {
			
			// Chiudiamo il server socket
			if ( tServerSocket != null) {
				try {
					tServerSocket.close();
				} catch (IOException e1) {}
				return;
			}
		}	
			
		// Segnala eventuali errori di IO
		catch (IOException e) {
			this.loggerNotify(Type.Error, "Impossibile aprire il "
					+ tSocketDescription, e.getMessage(), Verbosity.Low);
		}
		

	}
	

}
